SurfaceView介绍
SurfaceView是Andoird GUI系统中一种特殊的控件,它可以在非UI线程进行绘图。UI线程的绘制在view绘制流程一篇中介绍过了,本篇将对SurfaceView的绘制进行介绍。在此之前,我们看看SurfaceView的一般用法。
1 | public class SurfaceViewDemo extends SurfaceView |
SurfaceView的生命周期回调surfaceCreated->SurfaceChanged->surfaceDestroyed。这里在surfaceCreated中进行绘制,这个绘制可以在单独的线程中进行,并不依赖于ui线程。至于为何这样,后面我们再解释。
首先看看surfaceview的源码,先看其成员都有哪些
1 | public class SurfaceView extends View { |
在SurfaceView中有两个Surface绘图表面,所有绘制操作都是在这个绘图表面上进行的,这里有两个Surface是为了进行绘图时的前后台切换,这样当后台进行绘制时,前台可以显示之前绘制好的Surface表面。IWindowSession是用来和WMS进行会话的session,因为SurfaceView它本质上也是一个Window,它还有一个MyWindow成员,它类似于ViewRootImpl中的W对象,是用来和WMS进行通信交互的。因此它也是一个Binder对象,在WMS一端,它唯一标志了一个窗口对象Window。
SurfaceView的绘制流程
SurfaceView的绘制依然属于view树绘制的一部分,它依赖于宿主窗口,在View树的绘制流程中,我们看看SurfaceView是如何进行自身的绘制的,在performTraversals中绘制view树之前,会通知view树Attach到宿主窗口上,这是通过host.dispatchAttachedToWindow(attachInfo, 0)来实现的,这里的host即DecorView,它是个ViewGroup。在ViewGroup中又会调用子view的dispatchAttachedToWindow方法
1 | //ViewGroup |
对于子View,它的dispatchAttachedToWindow会回调onAttachedToWindow通知view被添加到宿主window上,随后宿主窗口可见,还会通过onWindowVisibilityChanged通知可见性发生了变化。这里我们看看SurfaceView它是如何做处理的。
1 | //surfaceView被添加到宿主窗口时回调 |
在SurfaceView的onAttachedToWindow中,SurfaceView会向父窗口请求设置一块透明区域,这个透明区域是为了使SurfaceView在宿主窗口中可见,因为SurfaceView被添加时它的Z序是小于宿主窗口的,即它是显示在宿主窗口下面的,要显示SurfaceView就需要在宿主窗口设置对应大小的透明区域。同时还会取到IWindowSession 用来和WMS进行通信。
1 |
|
关于可见性的变化,SurfaceView计算当前宿主窗口的可见性,并通过updateWindow来更新自身的窗口。
1 | private void updateWindow(boolean force, boolean redrawNeeded) { |
对于SurfaceView来说,它拥有独立的Window,这个Window同样受WMS的管理,因此在绘制时需要将该Window添加到WMS中去,同时,它也有它自己的绘图表面,因此在绘制之前需要计算它自身的窗口大小并向WMS请求绘图表面。关于分配绘图缓冲在其它章节已做了描述,本篇不再多做介绍。
SurfaceView的挖洞过程
SurfaceView的窗口类型一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,它在添加到宿主窗口上时,会在父窗口的表面设置一个透明区域以显示SurfaceView自身的内容,这是通过requestTransparentRegion来进行的,下面我们就看看这个过程是如何完成的。
1 | //ViewGroup |
SurfaceView请求透明区域的过程实际上为父View打上PFLAG_REQUEST_TRANSPARENT_REGIONS标记,这表示它要在宿主窗口上设置透明区域,这个过程直到ViewRootImpl,ViewRootImpl它也是ViewParent,它将调用requestLayout触发performTraversals来请求窗口重新布局和绘制,从而收集透明区域,最后通过WMS为该宿主窗口设置一个总的透明区域。
1 | private void performTraversals() { |
收集透明区域也是从DecorView开始的,它调用gatherTransparentRegion来完成,这个区域是由mTransparentRegion来存储维护的。如果透明区域发生了变化这时候就需要重新设置该透明区域到WMS中去。
1 | //ViewGroup.java |
收集透明区域会遍历父view下的所有子view的透明区域。在开始收集之前,首先将透明区域设置为DecorView视图的大小,然后遍历子view,如果view是不透明的区域则将其从初始的透明区域中移除,这样最后保留下来的就是需要设置的透明区域。
下面我们看看SurfaceView它是如何进行计算它的透明区域的。
1 |
|
SurfaceView首先判断窗口类型是否作为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,如果是则表示SurfaceView是用来作为应用面板的,这时候调用父类view的gatherTransparentRegion来处理,如果需要绘制,则将view的绘制区域从从参数region所描述的透明区域中移除。
SurfaceView的绘制
SurfaceView具有独立的绘图Surface,但它仍然属于View树中的子节点,它依附在宿主窗口上,所以它自身的view也是需要绘制到宿主窗口的Surface上的。在介绍view绘制流程一节中我们知道了view的绘制会触发draw和dispatchDraw方法,前者绘制它自身,后者负责绘制子view。
1 | //SurfaceView.java |
draw和dispatchDraw方法的参数canvas代表的是宿主窗口的绘图表面的画布,这表示surfaceview自身的ui是绘制在
宿主绘图表面上的,如果mWindowType不为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL表示SurfaceView不作为应用面板,那么将其view所占区域设置为黑色。
除了在宿主窗口上绘制ui,SurfaceView可以在自身的绘图表面上绘制内容,一般的绘制流程如下:
1 | SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view); |
需要注意的是,当绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS时,表示绘图表面的缓冲区不受我们控制,它是由摄像头或者视频播放服务来提供的。因此不能在随意在上面进行内容绘制。
1 |
|
SurfaceView的lockCanvas是从其绘图表面申请缓冲区,也就是Canvas,应用上层可以使用这个Canvas来进行内容的绘制,需要注意的是这个画布并不是线程安全的,需要通过mSurfaceLock的锁保护。